當多個線程同時對一個數據進行操作時,有可能會發生線程不安全的問題,這時候要使用synchronized
關鍵字將線程同步。
💡線程不安全的舉例:
今天有10張票券在進行銷售,同時有3個銷售人員在販售(線程),但是它們彼此之間不會知道誰已經銷售出去了(不同步),這時有可能A銷售人員去確認剩餘票券時,看到還有1張票,接著回去跟客人收錢(尚未將票拿走),B銷售人員接著也跑去確認剩餘票券,看到還有1張票,接著回去跟客人收錢(尚未將票拿走),這時A跟B同時都跟客人收了錢,但是只剩下1張票,票應該給誰呢?
synchronized(同步監視器) {
//需要被同步的程式碼
}
當線程執行在synchronized
中的程式碼時,會強迫其他使用到這個程式碼的線程等待,直到它完成,同步監視器又稱為鎖
,同步監視器必須
使用唯一
的物件,不限是甚麼類型
class SaleTicket implements Runnable {
int ticket = 100;
Object obj = new Object();
@Override
public void run() {
while(true) {
//也可以寫obj
// synchronized(obj) {
synchronized(this) {
if(tickek > 0) {
System.out.println(Thread.currentThread().getName() + "ticket:" + ticket);
} else {
break;
}
}
}
}
}
public class SaleTicket {
public static void main(String[] args) {
SaleTicket saleTicket = new SaleTicket();
Thread t1 = new Thread(saleTicket);
Thread t2 = new Thread(saleTicket);
Thread t3 = new Thread(saleTicket);
t1.start();
t2.start();
t3.start();
}
}
使用接口的方式進行線程的創建時,會是用多態
的概念將線程實例化,所以這邊可以使用this
當作synchronized
的參數
使用繼承的方式
class SaleTicket extends Thread {
int ticket = 100;
static Object obj = new Object();
@Override
public void run() {
while(true) {
//也可以寫obj
// synchronized(obj) {
// 💡不可以寫this,因為會有3個SaleTicket的實例物件
// synchronized(this) {
// Window.class會在反射的時候提到,相當於是Class clz = Window.class,記憶體中的Window類
synchronized(Window.class) {
if(tickek > 0) {
System.out.println(Thread.currentThread().getName() + "ticket:" + ticket);
} else {
break;
}
}
}
}
}
public class SaleTicket {
public static void main(String[] args) {
SaleTicket s1 = new SaleTicket();
SaleTicket s2 = new SaleTicket();
SaleTicket s3 = new SaleTicket();
s1.start();
s2.start();
s3.start();
}
}
實作接口
class SaleTicket implements Runnable {
int ticket = 100;
boolean isFlag = true;
@Override
public void run() {
while(isFlag) {
show();
}
}
// 可以將方法改為同步的
public synchronized void show() {
if(tickek > 0) {
System.out.println(Thread.currentThread().getName() + "ticket:" + ticket);
} else {
isFlag = flase;
}
}
}
public class SaleTicket {
public static void main(String[] args) {
SaleTicket saleTicket = new SaleTicket();
Thread t1 = new Thread(saleTicket);
Thread t2 = new Thread(saleTicket);
Thread t3 = new Thread(saleTicket);
t1.start();
t2.start();
t3.start();
}
}
當implements
Runnable
接口時,synchronized
方法時,同步監視器會自動的使用this
,這時不用擔心會出現非唯一
的問題發生。
使用繼承
class SaleTicket extends Thread {
int ticket = 100;
static Object obj = new Object();
@Override
public void run() {
while(isFlag) {
show();
}
}
//由於有3個實例,所以synchronized 指向的是各實例化的物件,這樣無法成功
//public synchronized void show() {
//可以使用static將方法轉為靜態方法,但是應該視情況而定,不要為了使用synchronized而加上static
public static synchronized void show() {
if(tickek > 0) {
System.out.println(Thread.currentThread().getName() + "ticket:" + ticket);
} else {
isFlag = flase;
}
}
}
public class SaleTicket {
public static void main(String[] args) {
SaleTicket s1 = new SaleTicket();
SaleTicket s2 = new SaleTicket();
SaleTicket s3 = new SaleTicket();
s1.start();
s2.start();
s3.start();
}
}
使用繼承
的方式時,由於每一個線程都是有自己的實例化
這時候synchronized
指向的會是自身實例化的物件所以非唯一
,可以使用static將方法轉為靜態方法,但是應該視情況而定,不要為了使用synchronized而加上static